#pragma once
#include <iostream>
#include <string>
#include <memory>

#include <cstdlib>
#include <cstring>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>

#include "InetAddr.hpp"
#include "Log.hpp"
using namespace std;

const static int gbacklog = 10;

class Socket;
using socket_sptr = shared_ptr<Socket>;

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR,
    USAGE_ERROR
};

// unique_ptr<Socket> listensock(new TcpSocket());
// listensock->BuildListenSocket(addr);//InetAddr addr;

// unique_ptr<Socket> clientsock (new TcpSocket());
// clientsock->BuildClientSocket();

// clientsock->send();
// clientsock->Recv();

class Socket
{
public:
    // 基本方法
    virtual void CreateSocketOrDie() = 0;

    // 将套接字信息与网络信息进行绑定时需要当前主机的IP地址与端口号
    virtual void BindSocketOrDie(InetAddr &addr) = 0;

    virtual void ListenSocketOrDie() = 0;

    virtual socket_sptr Accepter(InetAddr *addr) = 0; // 获取连接时,需要得到新的文件描述符&&需要得到客户端IP地址与端口号
    // addr输出型参数用于接收客户端的IP地址与端口号

    // 连接服务器时,需要目标主机的IP地址IP地址与端口号
    virtual bool Connector(InetAddr &addr) = 0;

    // 发送数据

    // 接收数据

public:
    // 模版方法
    void BuildListenSocket(InetAddr &addr)
    {
        CreateSocketOrDie();
        BindSocketOrDie(addr);
        ListenSocketOrDie();
    }
    bool BuildClientSocket(InetAddr &addr)
    {
        CreateSocketOrDie();
        return Connector(addr);
    }
};

class TcpSocket : public Socket
{
public:
    TcpSocket(int fd = -1)
        : _sockfd(fd)
    {
    }
    void CreateSocketOrDie() override
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "create sockfd error");
            exit(SOCKET_ERROR);
        }
        LOG(DEBUG, "socket create success, sockfd: %d", _sockfd);
    }
    void BindSocketOrDie(InetAddr &addr) override
    {
        // 构建目标主机信息
        struct sockaddr_in target;
        memset(&target, 0, sizeof(target));
        target.sin_family = AF_INET;
        target.sin_port = htons(addr.Port());
        target.sin_addr.s_addr = inet_addr(addr.IP().c_str());
        // 绑定
        int n = bind(_sockfd, (struct sockaddr *)&target, sizeof(target));
        if (n < 0)
        {
            LOG(FATAL, "bind error");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "bind success, sockfd: %d", _sockfd);
    }
    void ListenSocketOrDie() override
    {
        int n = listen(_sockfd, gbacklog);
        if (n < 0)
        {
            LOG(FATAL, "listen error");
            exit(LISTEN_ERROR);
        }
        LOG(DEBUG, "listen success, sockfd: %d", _sockfd);
    }

    socket_sptr Accepter(InetAddr *addr) override
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        int sockfd = accept(_sockfd, (struct sockaddr *)&peer, &len);
        if (sockfd < 0)
        {
            LOG(WARNING, "accept error");
            return nullptr;
        }

        *addr = peer; // InetAddr类需要重载赋值运算符

        // 由于外部需要sockfd来接收数据与发送数据,如何将sockfd传递给外部(类内)？
        // 创建一个新的TcpSocket对象,构造时传递新创建的描述符sockfd,Accepter()函数返回TcpSocket对象地址,
        // 便可以使用this->_sockfd访问新创建的文件描述符
        socket_sptr sock(new TcpSocket(sockfd));
        return sock;
    }

    // 客户端连接服务器时,需要目标主机(服务端)的IP地址IP地址与端口号
    // 连接成功返回true,否则返回false;
    bool Connector(InetAddr &addr) override
    {
        struct sockaddr_in target;
        memset(&target, 0, sizeof(target));
        target.sin_family = AF_INET;
        target.sin_port = htons(addr.Port());
        target.sin_addr.s_addr = inet_addr(addr.IP().c_str());

        int n = connect(_sockfd, (struct sockaddr *)&target, sizeof(target));
        if (n < 0)
        {
            cout << "connect error" << endl;
            return false;
        }
        return true;
    }

    int Sockfd()
    {
        return _sockfd;
    }

private:
    int _sockfd;
};